/**
 * \brief  ELF binary utility
 * \author Christian Helmuth
 * \date   2006-05-04
 *
 * XXX define some useful return values for error checking
 */

/*
 * Copyright (C) 2006-2009 Christian Helmuth
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/printf.h>
#include <base/elf.h>

#include <util/string.h>

using namespace Genode;

namespace Elf {

	#include "elf.h"
}


int Elf_binary::_ehdr_check_compat()
{
	using namespace Elf;

	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)_start;

	if (memcmp(ehdr, ELFMAG, SELFMAG) != 0) {
		printf("binary is not an ELF\n");
		return -1;
	}

	if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) {
		printf("support for 32-bit objects only\n");
		return -1;
	}

	if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
		printf("support for little endian only\n");
		return -1;
	}

	if (ehdr->e_type != ET_EXEC) {
		printf("program is no executable\n");
		return -1;
	}

	return 0;
}


int Elf_binary::_ph_table_check_compat()
{
	using namespace Elf;

	Elf32_Phdr *ph_table = (Elf32_Phdr *)_ph_table;
	unsigned num = _phnum;
	unsigned i;

	for (i = 0; i < num; i++) {
		if ((ph_table[i].p_type != PT_NULL) &&
		 (ph_table[i].p_type != PT_LOAD) &&
		 (ph_table[i].p_type != PT_GNU_STACK /* ignored */)) {
			printf(ESC_WRN "unsupported program segment type 0x%x\n" ESC_END,
			       ph_table[i].p_type);
			return -1;
		}
		if (ph_table[i].p_type == PT_LOAD)
			if (ph_table[i].p_align & (0x1000 - 1)) {
				printf(ESC_WRN "unsupported alignment 0x%x\n" ESC_END,
				       ph_table[i].p_align);
				return -1;
			}
	}

	return 0;
}


Elf_segment Elf_binary::get_segment(unsigned num)
{
	void *start;
	size_t offset, filesz, memsz;
	Elf_binary::Flags flags = { 0, 0, 0, 0 };

	if (!valid()) return Elf_segment();

	if (!(num < _phnum)) return Elf_segment();

	Elf::Elf32_Phdr *phdr = &((Elf::Elf32_Phdr *)_ph_table)[num];

	start  = (void *)phdr->p_vaddr;
	offset = phdr->p_offset;
	filesz = phdr->p_filesz;
	memsz  = phdr->p_memsz;

	flags.r = (phdr->p_flags & PF_R) ? 1 : 0;
	flags.w = (phdr->p_flags & PF_W) ? 1 : 0;
	flags.x = (phdr->p_flags & PF_X) ? 1 : 0;

	/* ignore this */
	if (phdr->p_type != PT_LOAD)
		flags.skip = 1;

	return Elf_segment(this, start, offset, filesz, memsz, flags);
}

/**
 * Constructor
 *
 * The object is only useful if valid() returns true.
 */
Elf_binary::Elf_binary(addr_t start)
: _valid(false), _start(start)
{
	Elf::Elf32_Ehdr *ehdr = (Elf::Elf32_Ehdr *)start;

	/* check for unsupported ELF features */
	if (_ehdr_check_compat()) return;

	/* program entry point */
	if (!(_entry = ehdr->e_entry)) return;

	/* segment tables */
	_ph_table  = _start + ehdr->e_phoff;
	_phentsize = ehdr->e_phentsize;
	_phnum     = ehdr->e_phnum;

	/* program segments */
	if (_ph_table_check_compat()) return;

	/* ready to rock */
	_valid = true;
}
